﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using CredentialManagement;

namespace ProxyFinder {
    public class WindowsCredentialProvider : DefaultCredentialProvider, IWindowsCredentialProvider {
        /// <summary>
        /// Returns a list of valid/possibly saved credentials for the given target.
        /// </summary>
        /// <param name="target">This is a key that the provider will use to look up credentials for.</param>
        /// <returns></returns>
        public override ICollection<ICredentials> GetCredentials(string target) {
            if(string.IsNullOrEmpty(target)) {
                throw new ArgumentNullException("target");
            }

            return GetCredentialInternal(target).Select(cred => {
                ICredentials newCredentials = new NetworkCredential {
                    UserName = cred.Username,
                    Password = cred.Password
                };
                // Dispose the current Credential object before we continue
                cred.Dispose();
                return newCredentials;
            }).ToList();
        }

        /// <summary>
        /// Prompt the user for credentials based on the set of provided arguments.
        /// </summary>
        /// <param name="target">The key for which to retrieve credentials for.</param>
        /// <param name="forcePrompt">Should the UI be forced to show even if the credentials are saved
        /// and can be returned without requiring user interaction
        /// </param>
        /// <returns></returns>
        public virtual ICredentials PromptForCredentials(string target, bool forcePrompt) {
            Credential savedCredentials = GetCredentialInternal(target).SingleOrDefault();
            if(!forcePrompt) {
                return null != savedCredentials
                           ? new NetworkCredential {
                                                       UserName = savedCredentials.Username,
                                                       Password = savedCredentials.Password
                                                   }
                           : PromptInternal(null, null, target);
            }
            return PromptInternal(savedCredentials.Username, savedCredentials.Password, target);
        }

        /// <summary>
        /// This is an internal method that supports setting the appropriate settings
        /// on the appropriate instance of the the Credential Prompt implementation
        /// and invokes the needed logic to get credentials for the given target.
        /// </summary>
        /// <param name="username">Username if any that can be used to pre-populate the prompt.</param>
        /// <param name="password">Password if any that can be used to pre-populate the prompt.</param>
        /// <param name="target">The key to use while searching for stored credentials and based on the user
        /// input such as checking of the Save Credentials checkbox will be used as the key to store
        /// credentials.
        /// </param>
        /// <returns></returns>
        private ICredentials PromptInternal(string username, string password, string target) {
            BaseCredentialsPrompt prompt = null;
            using(prompt = IsWinVistaOrHigher ? (BaseCredentialsPrompt)new VistaPrompt() : new XPPrompt()) {

                prompt.Title = string.Format("Please provide credentials for: {0}", target);
                if (prompt is XPPrompt) {
                    XPPrompt xpPrompt = prompt as XPPrompt;
                    xpPrompt.Target = target;
                    xpPrompt.AlwaysShowUI = true;
                }
                prompt.ShowSaveCheckBox = true;
                prompt.GenericCredentials = true;
                prompt.Username = username ?? string.Empty;
                prompt.Password = password ?? string.Empty;

                if (DialogResult.OK == prompt.ShowDialog()) {
                    if (string.IsNullOrEmpty(prompt.Username) || string.IsNullOrEmpty(prompt.Password)) {
                        throw new InvalidOperationException("Invalid Username or Password supplied.");
                    }
                    if (!prompt.ShowSaveCheckBox) {
                        return new NetworkCredential {
                                                         UserName = prompt.Username,
                                                         Password = prompt.Password
                                                     };
                    }
                    ICredentials newCredentials = new NetworkCredential {
                                                                            UserName = prompt.Username,
                                                                            Password = prompt.Password
                                                                        };
                    if (prompt.SaveChecked) {
                        Credential credential = new Credential(prompt.Username, prompt.Password);
                        credential.Save();
                        credential.Dispose();
                    }
                    return newCredentials;
                }
                return null;
            }
        }

        /// <summary>
        /// Call the CredentialManagement API to load existing saved credentials for the given Target.
        /// </summary>
        /// <param name="target">The key to use for looking up saved credentials.</param>
        /// <returns></returns>
        static ICollection<Credential> GetCredentialInternal(string target) {
            using(var set = new CredentialSet(target)) {
                // create a new list and load it with the found credentials
                // so that we can dispose of the CredentialSet object.
                ICollection<Credential> credentials = new List<Credential>(set.Load());
                set.Clear();
                return credentials;
            }
        }
        /// <summary>
        /// Returns true if the current OS is Vista or greater.
        /// </summary>
        bool IsWinVistaOrHigher {
            get {
                OperatingSystem OS = Environment.OSVersion;
                return (OS.Platform == PlatformID.Win32NT) && (OS.Version.Major >= 6);
            }
        }
    }
}